home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 2.toast / pc / sample code / processes / procdoggie / uprocessutils.p < prev   
Encoding:
Text File  |  2000-09-28  |  45.0 KB  |  1,204 lines

  1. {
  2.     File:        UProcessUtils.p
  3.  
  4.     Contains:    This unit is a high-level interface to the Process Manager.  It contains
  5.                 routines which allow you to launch a process, terminate a process, count the
  6.                 number of open processes, find a process given a file specification, and
  7.                 manage a list of documents for a process to open or print when it is
  8.                 launched.
  9.     
  10.                 The LaunchProcess routine is the most important routine in this unit.  It
  11.                 lets you launch either an application or a desk accessory and optionally lets
  12.                 you pass it a list of documents for the launched application to open or print
  13.                 (desk accessories don’t have document lists).
  14.  
  15.     Written by: Forrest Tanaka    
  16.  
  17.     Copyright:    Copyright © 1988-1999 by Apple Computer, Inc., All Rights Reserved.
  18.  
  19.                 You may incorporate this Apple sample source code into your program(s) without
  20.                 restriction. This Apple sample source code has been provided "AS IS" and the
  21.                 responsibility for its operation is yours. You are not permitted to redistribute
  22.                 this Apple sample source code as "Apple sample source code" after having made
  23.                 changes. If you're going to re-distribute the source, we require that you make
  24.                 it clear in the source that the code was descended from Apple sample source
  25.                 code, but that you've made changes.
  26.  
  27.     Change History (most recent first):
  28.                 7/27/1999    Karl Groethe    Updated for Metrowerks Codewarror Pro 2.1
  29.                 
  30.  
  31. }
  32.  
  33. UNIT UProcessUtils;
  34.  
  35. {[j=20/57/1$] Pasmat Options}
  36.  
  37.  
  38. INTERFACE
  39.  
  40.     USES
  41.         Processes;
  42.  
  43.  
  44. (*******************************************************************************
  45. * Used Units
  46. *******************************************************************************)
  47.  
  48.  
  49. (*******************************************************************************
  50. * Types
  51. *******************************************************************************)
  52.  
  53.     TYPE
  54.         (* Flag to indicate whether document list is for printing or opening *)
  55.         LaunchModeCode = (kJustLaunch, kOpenLaunch, kPrintLaunch);
  56.  
  57.         (* Document list entry for opening apps with docs to open or print *)
  58.         DocListEntryPtr = ^DocListEntryRec;
  59.         DocListEntryHnd = ^DocListEntryPtr;
  60.         DocListEntryRec = RECORD
  61.             next:    DocListEntryHnd; {Link to the next document list record}
  62.             docFile: FSSpec           {File specification}
  63.         END;
  64.  
  65.         (* Document list for opening apps with docs to open or print *)
  66.         DocListRec = RECORD
  67.             docList:   DocListEntryHnd; {Handle to the first document list entry}
  68.             openPrint: LaunchModeCode   {Opening or printing documents?}
  69.         END;
  70.         DocListPtr = ^DocListRec;
  71.         DocListHnd = ^DocListPtr;
  72.  
  73.  
  74. (*******************************************************************************
  75. * CreateDocList - Create a new document list
  76. *
  77. * This routine creates a new document list and returns a handle to it.  If there
  78. * isn’t enough memory for a new document list, CreateDocList returns NIL.
  79. *
  80. * If openOrPrint is kOpenLaunch, then the list of documents that are put into
  81. * the new document list will be opened when the application is launched.  If
  82. * openOrPrint is kPrintLaunch, then the list of documents will be printed when
  83. * the application is launched.  If kJustLaunch or any other value is passed in
  84. * openOrPrint, then no document list is created.  CreateDocList returns NIL in
  85. * this case.  This isn’t really a conflict with returning NIL when there’s not
  86. * enough memory for the new document list because the calling routine can check
  87. * to see if openOrPrint is valid or not itself.
  88. *******************************************************************************)
  89.  
  90.     FUNCTION CreateDocList (openOrPrint: LaunchModeCode): DocListHnd;
  91.  
  92.  
  93. (*******************************************************************************
  94. * IsEmptyDocList - Return TRUE if the specified document list is empty.
  95. *
  96. * IsEmptyDocList returns TRUE if the document list specified by "theList" is
  97. * empty.  “Empty” can mean either that theList is NIL or theList is a handle to
  98. * a document list that contains no documents.  It returns FALSE if there’s at
  99. * least one entry in "theList".
  100. *
  101. * theList must either be a valid handle to a document list or it must be NIL.
  102. * IsEmptyDocList is unpredictable if this isn’t true.
  103. *******************************************************************************)
  104.  
  105.     FUNCTION IsEmptyDocList (theList: DocListHnd): Boolean;
  106.  
  107.  
  108. (*******************************************************************************
  109. * AddToDocList - Add a document file specification to a document list
  110. *
  111. * AddToDocList adds the file specified by "newFile" to the end of the document
  112. * list specified by "theList".  If there isn’t enough memory to add a new file
  113. * to the document list, then AddToDocList returns memFullErr.  Otherwise, noErr
  114. * is returned.
  115. *
  116. * theList MUST be a valid handle to an initialized document list.  I won’t be
  117. * responsible for AddToDocList’s actions if this isn’t true.
  118. *******************************************************************************)
  119.  
  120.     FUNCTION AddToDocList (newFile: FSSpec;
  121.                            theList: DocListHnd): OSErr;
  122.  
  123.  
  124. (*******************************************************************************
  125. * DisposeDocList - Dispose of a document list
  126. *
  127. * DisposeDocList deallocates all the memory occupied by the document list
  128. * specified by the "theList" parameter.  If theList is NIL, then nothing is
  129. * done.  If it’s not a valid DocListHnd, DisposeDocList is unpredictable.
  130. *******************************************************************************)
  131.  
  132.     PROCEDURE DisposeDocList (theList: DocListHnd);
  133.  
  134.  
  135. (*******************************************************************************
  136. * WereInFront - Test to see if this application is in front or not
  137. *
  138. * This routine determines whether this application is the front-most application
  139. * or not.  If it is, then TRUE is returned.  If it isn’t, then FALSE is
  140. * returned.
  141. *******************************************************************************)
  142.  
  143.     FUNCTION WereInFront: Boolean;
  144.  
  145.  
  146. (*******************************************************************************
  147. * FindProcess - Find a process with specified file and name
  148. *
  149. * FindProcess searches the Process Manager’s process list for a process that was
  150. * launched from the file specified by "testFile".  If the process being searched
  151. * is a DA, then the "daName" parameter must specify the name of the desk
  152. * accessory (i.e. the name of the DRVR resource, including the initial null
  153. * character).  If the process is found, then information about the process is
  154. * returned in the "testFileInfo" parameter, and FindProcess returns TRUE.  If
  155. * the process could not be found, then FALSE is returned, and "testFileInfo" is
  156. * unchanged.
  157. *
  158. * Warning: the processName and processAppSpec fields of "testFileInfo" are NOT
  159. * valid when FindProcess returns TRUE.  If the name and FSSpec of the found file
  160. * are desired, then the calling routine must allocate space for those fields
  161. * and call GetProcessInfo itself, passing the ProcessInfoRec returned by this
  162. * routine as a parameter.
  163. *******************************************************************************)
  164.  
  165.     FUNCTION FindProcess (testFile:         FSSpec;
  166.                           daName:           StringPtr;
  167.                           VAR testFileInfo: ProcessInfoRec): Boolean;
  168.  
  169.  
  170. (*******************************************************************************
  171. * LaunchProcess - Launch the specified process
  172. *
  173. * This routine launches the process whose file has the location and name
  174. * specified by "processFile" parameter.  The process’s resulting serial number
  175. * is returned in the returnPSN parameter.  If the process to be launched is a
  176. * desk accessory, then the name of the desk accessory (including the initial
  177. * null character that desk accessories require) is passed in daName.  IF NIL is
  178. * passed in daName, then the first desk accessory found in the specified file
  179. * (according to the Resource Manager) is launched.  If an application is being
  180. * launched, then daName is ignored.  The options parameter specifies options to
  181. * use when launching the new process.  It has the same values and meanings as
  182. * the LaunchFlags type defined in the Process Manager chapter of Inside
  183. * Macintosh VI.  If a desk accessory is being launched, then only the
  184. * launchContinue flag has significance.
  185. *
  186. * A list of documents to be opened or printed by the launched application can
  187. * optionally be specified by the docList parameter.  If no documents are
  188. * specified, then docList should be NIL.  See the document list routines defined
  189. * early in this unit.
  190. *
  191. * LaunchProcess determines whether to launch an application or desk accessory
  192. * based on the type code of the file specified by processFile.  If the file’s
  193. * type is APPL, then LaunchProcess attempts to launch it as an application.  If
  194. * the file has any other type, then LaunchProcess attempts to launch a desk
  195. * accessory in that file.
  196. *
  197. * LaunchProcess returns two kinds of errors.  One error is returned as a
  198. * function result.  These kinds of errors are generated by any call that occurs
  199. * during the execution of LaunchProcess that is only used to manage the
  200. * launching of the specified application rather than launching the application
  201. * itself.   The launchError parameter returns the error code of any error that
  202. * occurs when the application is actually launched.
  203. *******************************************************************************)
  204.  
  205.     FUNCTION LaunchProcess (processFile:     FSSpec;
  206.                             daName:          StringPtr;
  207.                             docList:         DocListHnd;
  208.                             options:         LaunchFlags;
  209.                             VAR returnPSN:   ProcessSerialNumber;
  210.                             VAR launchError: OSErr): OSErr;
  211.  
  212.  
  213. (*******************************************************************************
  214. * CountProcesses - Count the number of open processes
  215. *
  216. * This routine searches through the Process Manager’s process list and counts
  217. * the number of open processes.  The result is returned.
  218. *******************************************************************************)
  219.  
  220.     FUNCTION CountProcesses: Integer;
  221.  
  222.  
  223. (*******************************************************************************
  224. * TerminateProcess - Terminate a process
  225. *
  226. * This routine causes the process specified by "theProcessNum" to be terminated.
  227. * If an error occurs, then the error code is returned.
  228. *******************************************************************************)
  229.  
  230.     FUNCTION TerminateProcess (theProcessNum: ProcessSerialNumber): OSErr;
  231.  
  232.  
  233. IMPLEMENTATION
  234.  
  235.     USES
  236.         AppleEvents
  237.         ,Aliases
  238.         ,Resources
  239.         ,TextUtils;
  240.  
  241.     CONST
  242.         kCaseSens = TRUE; {Pass to EqualString for case-sensitive check}
  243.         kDiacSens = TRUE; {Pass to EqualString for diacritical-sensitive check}
  244.  
  245.  
  246. {$S ProcessUtils}
  247. (*******************************************************************************
  248. * Private: EqualFSSpec - Check equality of FSSpec records
  249. *
  250. * EqualFSSpec returns TRUE if the file specified by spec1 refers to the same
  251. * file as the one specified by spec2.  Otherwise, EqualFSSpec returns FALSE.
  252. *
  253. * To compare names, I’m using EqualString with no case sensitivity, but with
  254. * sensitivity to diacriticals.  This isn’t usually the recommended way of
  255. * comparing strings, because the Script Manager has routines for comparing
  256. * strings in a more sophisticated, localizable way.  But the File Manager uses
  257. * _CmpString, which is the assembly language equivalent of EqualString, so
  258. * that’s the way I must do things here.
  259. *******************************************************************************)
  260.  
  261.     FUNCTION EqualFSSpec (spec1: FSSpec;
  262.                           spec2: FSSpec): Boolean;
  263.  
  264.     BEGIN
  265.         EqualFSSpec := (spec1.vRefNum = spec2.vRefNum) AND
  266.                        (spec1.parID = spec2.parID) AND
  267.                        (EqualString (spec1.name, spec2.name, NOT kCaseSens,
  268.                         kDiacSens))
  269.     END;
  270.  
  271.  
  272. {$S ProcessUtils}
  273. (*******************************************************************************
  274. * Public: CreateDocList
  275. *
  276. * A document list record is allocated and initialized.  Pretty simple.
  277. *******************************************************************************)
  278.  
  279.     FUNCTION CreateDocList (openOrPrint: LaunchModeCode): DocListHnd;
  280.  
  281.         VAR
  282.             newList: DocListHnd; {Handle to the new DocListRec}
  283.  
  284.     BEGIN
  285.         IF (openOrPrint = kOpenLaunch) OR (openOrPrint = kPrintLaunch) THEN
  286.             BEGIN
  287.                 newList := DocListHnd(NewHandle (SIZEOF (DocListRec)));
  288.                 IF newList <> NIL THEN
  289.                     BEGIN
  290.                         newList^^.docList := NIL;
  291.                         newList^^.openPrint := openOrPrint
  292.                     END;
  293.                 CreateDocList := newList
  294.             END
  295.     END;
  296.  
  297.  
  298. {$S ProcessUtils}
  299. (*******************************************************************************
  300. * Public: IsEmptyDocList
  301. *
  302. * If the document list record is NIL, or if the docList field is NIL, then the
  303. * given document list is empty.
  304. *******************************************************************************)
  305.  
  306.     FUNCTION IsEmptyDocList (theList: DocListHnd): Boolean;
  307.  
  308.     BEGIN
  309.         IsEmptyDocList := (theList = NIL) | (theList^^.docList = NIL)
  310.     END;
  311.  
  312.  
  313. {$S ProcessUtils}
  314. (*******************************************************************************
  315. * Public: AddToDocList
  316. *
  317. * A new document list entry is allocated and then "newFile" is copied into its
  318. * docFile field.  It is then added to end of "theList".
  319. *******************************************************************************)
  320.  
  321.     FUNCTION AddToDocList (newFile: FSSpec;
  322.                            theList: DocListHnd): OSErr;
  323.  
  324.         VAR
  325.             newEntry:  DocListEntryHnd; {Handle to the new DocListEntryRec}
  326.             currEntry: DocListEntryHnd; {Handle to DocListEntryRec being checked}
  327.  
  328.     BEGIN
  329.         (* Create a new document list entry *)
  330.         newEntry := DocListEntryHnd (NewHandle (SIZEOF (DocListEntryRec)));
  331.         IF newEntry <> NIL THEN
  332.             BEGIN
  333.                 (* Initialize the new document list entry *)
  334.                 newEntry^^.next := NIL;
  335.                 newEntry^^.docFile := newFile;
  336.  
  337.                 IF IsEmptyDocList (theList) THEN
  338.                     theList^^.docList := newEntry
  339.                 ELSE
  340.                     BEGIN
  341.                         (* Start from the head of the list *)
  342.                         currEntry := theList^^.docList;
  343.  
  344.                         (* Find the end of the list *)
  345.                         WHILE currEntry^^.next <> NIL DO
  346.                             currEntry := currEntry^^.next;
  347.  
  348.                         (* Attach the new entry to the end of the list *)
  349.                         currEntry^^.next := newEntry
  350.                     END;
  351.                 AddToDocList := noErr
  352.             END
  353.         ELSE
  354.             AddToDocList := MemError
  355.     END;
  356.  
  357.  
  358. {$S ProcessUtils}
  359. (*******************************************************************************
  360. * Private: DisposeAllDocEntries - Dispose of all document list entries
  361. *
  362. * Ahhh. . . good old, basic, text-book recursion.  This routine has a very small
  363. * stack frame, so the recursion isn’t very expensive in this case.  Document
  364. * lists are normally very short too.
  365. *
  366. * This routine disposes of every document list entry in a linked list of
  367. * document list entries starting from the entry docListEntry.  If docListEntry
  368. * isn’t NIL and it isn’t a valid DocListEntryHnd, DisposeAllDocEntries probably
  369. * blows up in very bad ways.
  370. *******************************************************************************)
  371.  
  372.     PROCEDURE DisposeAllDocEntries (docListEntry: DocListEntryHnd);
  373.  
  374.     BEGIN
  375.         IF docListEntry <> NIL THEN
  376.             BEGIN
  377.                 IF docListEntry^^.next <> NIL THEN
  378.                     DisposeAllDocEntries (docListEntry^^.next);
  379.                 DisposeHandle (Handle(docListEntry))
  380.             END
  381.     END;
  382.  
  383.  
  384. {$S ProcessUtils}
  385. (*******************************************************************************
  386. * Public: DisposeDocList
  387. *
  388. * Should be pretty obvious how this works.
  389. *******************************************************************************)
  390.  
  391.     PROCEDURE DisposeDocList (theList: DocListHnd);
  392.  
  393.     BEGIN
  394.         IF theList <> NIL THEN
  395.             BEGIN
  396.                 (* Dispose of all document list entries *)
  397.                 DisposeAllDocEntries (theList^^.docList);
  398.  
  399.                 (* Dispose of the document list record *)
  400.                 DisposeHandle (Handle(theList))
  401.             END
  402.     END;
  403.  
  404.  
  405. {$S ProcessUtils}
  406. (*******************************************************************************
  407. * Public: WereInFront
  408. *
  409. * The current application’s process serial number can always be represented by
  410. * setting the high long integer to 0 and setting the low long integer to the
  411. * constant kCurrentProcess.  This process serial number is compared against the
  412. * process serial number of the front application.  If they’re the same, then
  413. * this application is the front application.
  414. *******************************************************************************)
  415.  
  416.     FUNCTION WereInFront: Boolean;
  417.  
  418.         VAR
  419.             thisProcess:  ProcessSerialNumber; {Current process’s PSN}
  420.             frontProcess: ProcessSerialNumber; {Front process’s PSN}
  421.             inFront:      Boolean;             {TRUE if we’re in front}
  422.             error:        OSErr;
  423.  
  424.         PROCEDURE RecoverError (errorCode: OSErr);
  425.  
  426.         BEGIN
  427.             {$unused errorCode}
  428.             WereInFront := TRUE;
  429.             EXIT (WereInFront)
  430.         END;
  431.  
  432.     BEGIN
  433.         (* Make a PSN for this application *)
  434.         thisProcess.highLongOfPSN := 0;
  435.         thisProcess.lowLongOfPSN := kCurrentProcess;
  436.  
  437.         (* Get the PSN of the front process *)
  438.         error := GetFrontProcess ((*<*)frontProcess);
  439.         IF error <> noErr THEN
  440.             RecoverError (error);
  441.  
  442.         (* See if this application is the front process *)
  443.         error := SameProcess (thisProcess, frontProcess, (*<*)inFront);
  444.         IF error <> noErr THEN
  445.             RecoverError (error);
  446.  
  447.         WereInFront := inFront
  448.     END;
  449.  
  450.  
  451. {$S ProcessUtils}
  452. (*******************************************************************************
  453. * Private: CreateDocListDesc - Create an AE descriptor for a list of documents
  454. *
  455. * This routine takes a document list in the "docList" parameter and converts it
  456. * to an 'appa' (typeAppParameters) AppleEvent descriptor containing a list of
  457. * documents to be opened by the Process Manager’s LaunchApplication routine.
  458. * This descriptor is returned.
  459. *
  460. * To do this, we have to first build an AppleEvent for an 'odoc'
  461. * (kAEOpenDocuments) or a 'pdoc' (kAEPrintDocuments) event that contains the
  462. * same list of documents that are contained in the "theDocList" parameter.  Once
  463. * that’s done, this AppleEvent is coerced into an typeAppParameters descriptor.
  464. *
  465. * To create the kAEOpenDocuments or kAEPrintDocuments AppleEvent, a target
  466. * address descriptor is needed.  Because we’re converting the AppleEvent into a
  467. * typeAppParameters descriptor rather than sending it somewhere, it doesn’t
  468. * matter what target address we use.  I just used the process serial number
  469. * (PSN) of this application as a dummy value.  Because this is an application
  470. * and not something like a driver, I can just use the "kCurrentProcess" constant
  471. * to represent this application’s PSN.  A descriptor is made of this PSN, then
  472. * this PSN is used when creating the new kAEOpenDocuments AppleEvent.  Once this
  473. * AppleEvent is created, the PSN descriptor is no longer needed and is disposed
  474. * of.
  475. *
  476. * kAEOpenDocuments AppleEvents needs a list of file aliases; each file alias
  477. * represents a document to open.  To do this, a new descriptor list is created,
  478. * and an alias record is created.  Then, each document in the "theDocList"
  479. * parameter is converted to an alias in the alias record, then the alias record
  480. * is manually converted into an 'alis' (typeAlias) descriptor.  I did this
  481. * manually because an alias is already a handle, and it didn’t seem necessary to
  482. * go through the extra expense of the AECreateDesc routine.  This typeAlias
  483. * descriptor is then added to the descriptor list we created earlier in this
  484. * paragraph.  Once the entire document list is converted to the descriptor list,
  485. * the alias record is no longer needed and is disposed of.
  486. *
  487. * This descriptor list full of document aliases is then added to the
  488. * kAEOpenDocuments AppleEvent I created.  Because the descriptor list is copied
  489. * into the AppleEvent, it is disposed of after the AEPutParamDesc call.
  490. *
  491. * Then, the most crucial call is made, AECoerceDesc.  This routine coerces the
  492. * AppleEvent we just made into a typeAppParameters descriptor.  The record
  493. * specified by the dataHandle of this descriptor is exactly in the same format
  494. * as the AppParameters record defined in the “Launching Other Applications”
  495. * section of the Process Manager chapter of Inside Macintosh VI.
  496. *
  497. * You can HLock the dataHandle, then set the "launchAppParameters" field of the
  498. * LaunchParamBlockRec to the dereferenced dataHandle.  LaunchApplication can
  499. * then launch the specified application with the document list that was created.
  500. * If the launched application is high-level event aware (as specified in its
  501. * SIZE resource), the Process Manager converts LaunchAppParameters back to a
  502. * real high-level event which is processed by the application to (hopefully)
  503. * open the documents.  Remember that LaunchAppParameters can be any high-level
  504. * event, but it’ll usually be used to pass an 'odoc' or 'pdoc' event, and that’s
  505. * what we’re using here.  If the launched application isn’t high-level event
  506. * aware, and if LaunchAppParameters is an 'odoc' or 'pdoc' event, then the
  507. * Process Manager will convert the 'odoc' or 'pdoc' event into the old-style
  508. * application parameters so that an old-style application can still open
  509. * documents properly.
  510. *
  511. * This routine is important.  Read it.  Understand it.  Then teach me how it
  512. * works.
  513. *******************************************************************************)
  514.  
  515.     FUNCTION CreateDocListDesc (theDocList:     DocListHnd;
  516.                                 VAR launchDesc: AEDesc): OSErr;
  517.  
  518.         CONST
  519.             kPutAtEnd = 0; {For AEPutDesc; put new descriptor at end of list}
  520.  
  521.         VAR
  522.             applMessage:  AppleEvent;          {odoc/pdoc event w/ spec’d doc list}
  523.             docDescList:  AEDescList;          {Desc list of doc alias descs}
  524.             selfPSN:      ProcessSerialNumber; {Our own PSN; not really used here}
  525.             selfAddress:  AEDesc;              {Desc for our own PSN; ditto}
  526.             docDesc:      AEDesc;              {Desc for doc alias}
  527.             docAlias:     AliasHandle;         {Alias for specified docs}
  528.             currDocEntry: DocListEntryHnd;     {Handle to current doc list entry}
  529.             openPrintCmd: AEEventID;           {odoc or pdoc event?}
  530.             wasChanged:   Boolean;             {TRUE if UpdateAlias changed alias}
  531.             error:        OSErr;
  532.  
  533.         PROCEDURE RecoverError (errorCode: Integer);
  534.  
  535.         BEGIN
  536.             IF selfAddress.dataHandle <> NIL THEN
  537.                 error := AEDisposeDesc ((*◊*)selfAddress);
  538.             IF applMessage.dataHandle <> NIL THEN
  539.                 error := AEDisposeDesc ((*◊*)applMessage);
  540.             IF docDescList.dataHandle <> NIL THEN
  541.                 error := AEDisposeDesc ((*◊*)docDescList);
  542.             IF docAlias <> NIL THEN
  543.                 DisposeHandle (Handle(docAlias));
  544.             CreateDocListDesc := errorCode;
  545.             EXIT (CreateDocListDesc)
  546.         END;
  547.  
  548.     BEGIN
  549.         selfAddress.dataHandle := NIL;
  550.         applMessage.dataHandle := NIL;
  551.         docDescList.dataHandle := NIL;
  552.         docAlias := NIL;
  553.  
  554.         (* Make descriptor for my own PSN; just for AECreateAppleEvent’s yuks *)
  555.         selfPSN.highLongOfPSN := 0;
  556.         selfPSN.lowLongOfPSN := kCurrentProcess;
  557.         error := AECreateDesc (typeProcessSerialNumber, @selfPSN,
  558.                 SIZEOF (ProcessSerialNumber), (*<*)selfAddress);
  559.         IF error <> noErr THEN
  560.             RecoverError (error);
  561.  
  562.         (* Create an AppleEvent for our list of document descriptors *)
  563.         IF theDocList^^.openPrint = kOpenLaunch THEN
  564.             openPrintCmd := kAEOpenDocuments
  565.         ELSE IF theDocList^^.openPrint = kPrintLaunch THEN
  566.             openPrintCmd := kAEPrintDocuments;
  567.         error := AECreateAppleEvent (kCoreEventClass, openPrintCmd, selfAddress,
  568.                 kAutoGenerateReturnID, kAnyTransactionID, (*<*)applMessage);
  569.         IF error <> noErr THEN
  570.             RecoverError (error);
  571.  
  572.         (* PSN copied into the odoc or pdoc event, so don’t need it anymore *)
  573.         error := AEDisposeDesc ((*◊*)selfAddress);
  574.  
  575.         (* Create list of descriptors for files; don’t use list factorization *)
  576.         error := AECreateList (NIL, 0, FALSE, (*<*)docDescList);
  577.         IF error <> noErr THEN
  578.             RecoverError (error);
  579.  
  580.         (* Create an alias for the first document *)
  581.         currDocEntry := theDocList^^.docList;
  582.         HLock (Handle(currDocEntry));
  583.         error := NewAlias (NIL, currDocEntry^^.docFile, (*<*)docAlias);
  584.         HUnlock (Handle(currDocEntry));
  585.         IF error <> noErr THEN
  586.             RecoverError (error);
  587.  
  588.         (* Put each document in the document list into document descriptor list *)
  589.         WHILE currDocEntry <> NIL DO
  590.             BEGIN
  591.                 (* Convert alias into an alias descriptor manually *)
  592.                 docDesc.descriptorType := typeAlias;
  593.                 docDesc.dataHandle := Handle(docAlias);
  594.  
  595.                 (* Put the alias descriptor into the document descriptor list *)
  596.                 error := AEPutDesc ((*◊*)docDescList, kPutAtEnd, docDesc);
  597.                 IF error <> noErr THEN
  598.                     RecoverError (error);
  599.  
  600.                 (* Go to the next document in the document list *)
  601.                 currDocEntry := currDocEntry^^.next;
  602.  
  603.                 (* Convert the next document’s FSSpec into the alias *)
  604.                 IF currDocEntry <> NIL THEN
  605.                     BEGIN
  606.                         HLock (Handle(currDocEntry));
  607.                         error := UpdateAlias  (NIL, currDocEntry^^.docFile,
  608.                                 (*<*)docAlias, (*<*)wasChanged);
  609.                         HUnlock (Handle(currDocEntry));
  610.                         IF error <> noErr THEN
  611.                             RecoverError (error)
  612.                     END
  613.             END;
  614.  
  615.         (* All aliases are in docDescList, so don’t need the alias record *)
  616.         DisposeHandle (Handle(docAlias));
  617.         docAlias := NIL;
  618.  
  619.         (* Put the descriptor list of documents into the odoc or pdoc event *)
  620.         error := AEPutParamDesc ((*◊*)applMessage, keyDirectObject, docDescList);
  621.         IF error <> noErr THEN
  622.             RecoverError (error);
  623.  
  624.         (* The descriptor list is copied into the odoc event, so get rid of it *)
  625.         error := AEDisposeDesc (docDescList);
  626.  
  627.         (* Convert the odoc event to a descriptor suitable for the launch PB *)
  628.         error := AECoerceDesc (applMessage, typeAppParameters, (*<*)launchDesc);
  629.         IF error <> noErr THEN
  630.             RecoverError (error);
  631.  
  632.         (* We’re really interested in launchDesc, so don’t need odoc message *)
  633.         error := AEDisposeDesc (applMessage);
  634.  
  635.         CreateDocListDesc := noErr
  636.     END;
  637.  
  638.  
  639. {$S ProcessUtils}
  640. (*******************************************************************************
  641. * Private: SendOpenAppEvent - Send an 'oapp' event to an application
  642. *
  643. * When an application that is high-level event aware (as indicated by the SIZE
  644. * resource flag), it won’t open an untitled document at launch until it gets an
  645. * 'oapp' AppleEvent.  This routine sends an 'oapp' event to the application
  646. * specified by the processNum parameter.
  647. *
  648. * First, a descriptor is created that contains the process serial number of the
  649. * target application.  This descriptor is then added to the 'oapp' AppleEvent.
  650. * Finally, this AppleEvent is sent to the target application which should open
  651. * an untitled document in response.
  652. *
  653. * This routine isn’t called if the application is to open specific documents
  654. * when it’s launched.
  655. *
  656. * If an error occurs, the error code is returned.
  657. *******************************************************************************)
  658.  
  659.     FUNCTION SendOpenAppEvent (processNum: ProcessSerialNumber): OSErr;
  660.  
  661.         VAR
  662.             theBaby:   AEAddressDesc; {PSN desc. of process that’s been opened}
  663.             openEvent: AppleEvent;    {'oapp' AppleEvent}
  664.             reply:     AppleEvent;    {Reply from receiving application; ignored}
  665.             error:     OSErr;
  666.  
  667.         PROCEDURE RecoverError (errorCode: Integer);
  668.  
  669.         BEGIN
  670.             IF theBaby.dataHandle <> NIL THEN
  671.                 error := AEDisposeDesc ((*◊*)theBaby);
  672.             IF openEvent.dataHandle <> NIL THEN
  673.                 error := AEDisposeDesc ((*◊*)openEvent);
  674.             IF reply.dataHandle <> NIL THEN
  675.                 error := AEDisposeDesc ((*◊*)reply);
  676.             SendOpenAppEvent := errorCode;
  677.             EXIT (SendOpenAppEvent)
  678.         END;
  679.  
  680.     BEGIN
  681.         theBaby.dataHandle := NIL;
  682.         openEvent.dataHandle := NIL;
  683.         reply.dataHandle := NIL;
  684.  
  685.         (* Create the Process Serial Number event descriptor *)
  686.         error := AECreateDesc (typeProcessSerialNumber, Ptr(@processNum),
  687.                 SIZEOF (ProcessSerialNumber), (*<*)theBaby);
  688.         IF error <> noErr THEN
  689.             RecoverError (error);
  690.  
  691.         (* Create 'oapp' event with the specified process serial number *)
  692.         error := AECreateAppleEvent (kCoreEventClass, kAEOpenApplication,
  693.                 theBaby, kAutoGenerateReturnID, kAnyTransactionID, (*<*)openEvent);
  694.         IF error <> noErr THEN
  695.             RecoverError (error);
  696.  
  697.         (* Send the 'oapp' event *)
  698.         error := AESend (openEvent, (*<*)reply, kAENoReply, kAENormalPriority,
  699.                 0, NIL, NIL);
  700.         IF error <> noErr THEN
  701.             RecoverError (error);
  702.  
  703.         (* Dispose of the descriptor and event *)
  704.         error := AEDisposeDesc ((*◊*)theBaby);
  705.         error := AEDisposeDesc ((*◊*)openEvent);
  706.         IF reply.dataHandle <> NIL THEN
  707.             error := AEDisposeDesc ((*◊*)reply)
  708.     END;
  709.  
  710.  
  711. {$S ProcessUtils}
  712. (*******************************************************************************
  713. * Private: LaunchProcApp - Launch an application
  714. *
  715. * This routine saves a bit of work when launching an application because it sets
  716. * up the launch parameter block and manages the construction of the application
  717. * list descriptor.  The application to open is specified in processFile.  If
  718. * there are any documents for the application to open when it’s launched, then
  719. * the document list is passed in docList.  If there aren’t any documents for the
  720. * application to open, then docList is NIL.  The options parameter specifies the
  721. * options to use when launching.  This contains the values for the LaunchFlags
  722. * data type specified in the Process Manager chapter of Inside Macintosh VI.
  723. *
  724. * The process serial number of the specified process is returned in the
  725. * processNum parameter.  The function result and the launchError parameter both
  726. * return error codes.  One error is returned as a function result.  These kinds
  727. * of errors are generated by any call that occurs during the execution of
  728. * LaunchProcess that is only used to manage the launching of the specified
  729. * application rather than launching the application itself.   The launchError
  730. * parameter returns the error code of any error that occurs when the application
  731. * is actually launched.
  732. *******************************************************************************)
  733.  
  734.     FUNCTION LaunchProcApp (processFile:     FSSpec;
  735.                             docList:         DocListHnd;
  736.                                     options:         LaunchFlags;
  737.                             VAR processNum:  ProcessSerialNumber;
  738.                             VAR launchError: OSErr): OSErr;
  739.  
  740.         VAR
  741.             launchParms: LaunchParamBlockRec; {Parameters for launching a file}
  742.             launchDesc:  AEDesc;              {Document file list descriptor}
  743.             appParms:    AppParametersPtr;    {Pointer to the app parameters}
  744.             error:       OSErr;
  745.  
  746.         PROCEDURE RecoverError (errorCode: OSErr);
  747.  
  748.         BEGIN
  749.             IF launchDesc.dataHandle <> NIL THEN
  750.                 BEGIN
  751.                     HUnlock (Handle(launchDesc.dataHandle));
  752.                     error := AEDisposeDesc (launchDesc)
  753.                 END;
  754.             LaunchProcApp := errorCode;
  755.             EXIT (LaunchProcApp)
  756.         END;
  757.  
  758.     BEGIN
  759.         launchError := noErr;
  760.         launchDesc.dataHandle := NIL;
  761.  
  762.         (* Create the document list descriptor, if there’s a document list *)
  763.         IF docList <> NIL THEN
  764.             BEGIN
  765.                 error := CreateDocListDesc (docList, (*<*)launchDesc);
  766.                 IF error <> noErr THEN
  767.                     RecoverError (error);
  768.                 HLock (Handle(launchDesc.dataHandle));
  769.  
  770.                 (* Descriptor dataHandle in format suitable for LaunchApplication *)
  771.                 appParms := AppParametersPtr(launchDesc.dataHandle^)
  772.             END
  773.         ELSE
  774.             appParms := NIL;
  775.  
  776.         (* Set up the launch parameters *)
  777.         WITH launchParms DO
  778.             BEGIN
  779.                 (*WITH*)launchBlockID := extendedBlock;
  780.                 (*WITH*)launchEPBLength := extendedBlockLen;
  781.                 (*WITH*)launchFileFlags := 0;
  782.                 (*WITH*)launchControlFlags := options;
  783.                 (*WITH*)launchAppSpec := @processFile;
  784.                 (*WITH*)launchAppParameters := appParms
  785.             END;
  786.  
  787.         (* Launch the process *)
  788.         launchError := LaunchApplication (@launchParms);
  789.  
  790.         (* Get rid of the document descriptor list, if there was one *)
  791.         IF docList <> NIL THEN
  792.             BEGIN
  793.                 HUnlock (Handle(launchDesc.dataHandle));
  794.                 error := AEDisposeDesc (launchDesc)
  795.             END
  796.         ELSE
  797.             (* No document descriptor list, so send oapp AppleEvent *)
  798.             IF launchError = noErr THEN
  799.                 error := SendOpenAppEvent (launchParms.launchProcessSN);
  800.  
  801.         (* If the launch was successful, return the PSN of the process *)
  802.         IF launchError = noErr THEN
  803.             processNum := launchParms.launchProcessSN;
  804.  
  805.         LaunchProcApp := noErr
  806.     END;
  807.  
  808.  
  809. {$S ProcessUtils}
  810. (*******************************************************************************
  811. * Private: GetFirstDAName - Get name of first 
  812. *
  813. * GetFirstDAName gets the name of the first desk accessory in the file specified
  814. * by daFile.  This name is returned in the daName parameter.  GetFirstDAName
  815. * returns TRUE if a desk accessory was found in the file.  If no desk accessory
  816. * could be found, then GetFirstDAName returns FALSE and the daName parameter is
  817. * unchanged.
  818. *
  819. * Desk accessories are stored in DRVR resources, but device drivers are also
  820. * stored in DRVR resources.  GetFirstDAName will not return the names of device
  821. * drivers.  How do you tell whether a DRVR resource is a desk accessory or a
  822. * device driver?  Simply check on the first character of the DRVR resource’s
  823. * name.  If it’s a null character, then you’ve got a desk accessory.  If it’s
  824. * any other character, then you’ve got a device driver.
  825. *******************************************************************************)
  826.  
  827.     FUNCTION GetFirstDAName (daFile:     FSSpec;
  828.                              VAR daName: Str255): Boolean;
  829.  
  830.         VAR
  831.             currRF:   Integer; {Ref num of current resource file}
  832.             daRF:     Integer; {Ref num of DA’s resource file}
  833.             drvrRsrc: Handle;  {Handle to a DRVR resource}
  834.             drvrID:   Integer; {ID number of drvrRsrc}
  835.             drvrType: ResType; {Type of resource}
  836.             drvrName: Str255;  {Name of resource}
  837.             numDRVRs: Integer; {Number of DRVR resources in the file}
  838.             index:    Integer; {Index into DRVR resources}
  839.             daFound:  Boolean; {TRUE if a DA was found in daFile}
  840.  
  841.     BEGIN
  842.         daFound := FALSE;
  843.  
  844.         (* Save the current resource file ref num so we can restore it later *)
  845.         currRF := CurResFile;
  846.  
  847.         (* Open the specified resource file *)
  848.         daRF := FSpOpenResFile (daFile, fsRdPerm);
  849.         IF daRF <> -1 THEN
  850.             BEGIN
  851.                 (* Ready to check each DRVR; only checking, so set ResLoad false *)
  852.                 SetResLoad (FALSE);
  853.                 index := 1;
  854.                 numDRVRs := Count1Resources ('DRVR');
  855.  
  856.                 (* Check each DRVR until all checked or a DA was found *)
  857.                 WHILE (index <= numDRVRs) AND (NOT daFound) DO
  858.                     BEGIN
  859.                         drvrRsrc := Get1IndResource ('DRVR', index);
  860.  
  861.                         (* Get information about the resource *)
  862.                         IF drvrRsrc <> NIL THEN
  863.                             BEGIN
  864.                                 GetResInfo (drvrRsrc, (*<*)drvrID, (*<*)drvrType,
  865.                                         (*<*)drvrName);
  866.  
  867.                                 (* Find out whether it’s a DA or not *)
  868.                                 IF (drvrName [0] > CHR (0)) & (drvrName [1] = CHR (0))
  869.                                         THEN
  870.                                     daFound := TRUE
  871.                                 ELSE
  872.                                     index := SUCC (index)
  873.                             END
  874.                     END;
  875.  
  876.                 (* Restore the resource file and reset ResLoad *)
  877.                 SetResLoad (TRUE);
  878.                 CloseResFile (daRF)
  879.             END;
  880.  
  881.         (* Get name of first DA in the file, or nil string if none found *)
  882.         IF daFound THEN
  883.             daName := drvrName
  884.         ELSE
  885.             daName [0] := CHR (0);
  886.         GetFirstDAName := daFound;
  887.         UseResFile (currRF)
  888.     END;
  889.  
  890.  
  891. {$S ProcessUtils}
  892. (*******************************************************************************
  893. * Public: FindProcess
  894. *
  895. * The critical routine called by FindProcess is the GetProcessInfo routine.  The
  896. * process information it returns is checked against "testFile", and against
  897. * "daName" if a desk accessory is being checked.  If they’re equal, then
  898. * FindProcess returns TRUE and puts the process information in "testFileInfo".
  899. * If the entire Process Manager process list is searched without finding a
  900. * match, then FALSE is returned.
  901. *******************************************************************************)
  902.  
  903.     FUNCTION FindProcess (testFile:         FSSpec;
  904.                           daName:           StringPtr;
  905.                           VAR testFileInfo: ProcessInfoRec): Boolean;
  906.  
  907.         VAR
  908.             procInfo:  ProcessInfoRec;      {Information on process being checked}
  909.             procNum:   ProcessSerialNumber; {Serial num of process being checked}
  910.             procName:  Str255;              {Name of open process}
  911.             procSpec:  FSSpec;              {File spec of open process’s file}
  912.             procFound: Boolean;             {TRUE if matching DA was found}
  913.             error:     OSErr;
  914.  
  915.     BEGIN
  916.         (* Start checking from first process in Process Manager’s list *)
  917.         procNum.highLongOfPSN := 0;
  918.         procNum.lowLongOfPSN := kNoProcess;
  919.  
  920.         (* Loop through entire list of open processes or until match *)
  921.         procFound := FALSE;
  922.         WHILE (NOT procFound) & (GetNextProcess ((*◊*)procNum) = noErr) DO
  923.             BEGIN
  924.                 (* Get information about an open process *)
  925.                 procInfo.processInfoLength := SIZEOF (ProcessInfoRec);
  926.                 procInfo.processName := @procName;
  927.                 procInfo.processAppSpec := @procSpec;
  928.                 error := GetProcessInformation (procNum, (*◊*)procInfo);
  929.  
  930.                 (* Is it the same file as the one we’re testing? *)
  931.                 IF error <> noErr THEN
  932.                     IF EqualFSSpec (procInfo.processAppSpec^, testFile) THEN
  933.                         (* Yes; if it’s an application, we’ve found matching app *)
  934.                         IF BAND (procInfo.processMode, modeDeskAccessory) = 0 THEN
  935.                             procFound := TRUE
  936.                         ELSE
  937.                             (* Is it the same DA as the one we’re testing? *)
  938.                             IF EqualString (procInfo.processName^, daName^,
  939.                                     NOT kCaseSens, kDiacSens) THEN
  940.                                 (* Yes, we’ve found the matching DA *)
  941.                                 procFound := TRUE
  942.             END;
  943.  
  944.         IF procFound THEN
  945.             testFileInfo := procInfo;
  946.         FindProcess := procFound
  947.     END;
  948.  
  949.  
  950. {$S ProcessUtils}
  951. (*******************************************************************************
  952. * Private: LaunchProcDA - Launch a desk accessory
  953. *
  954. * This routine launches the desk accessory that’s in the file specified by
  955. * process file and with the name specified by daName.  If daName is NIL, then
  956. * the first desk accessory in the file, according to Get1IndResource, is
  957. * launched.
  958. *
  959. * As of 7.0b1, LaunchDeskAccessory doesn’t quite work as advertised in Inside
  960. * Macintosh VI if the desk accessory that it’s launching is already open.
  961. * Inside Macintosh VI says that the open desk accessory that’s being launched is
  962. * simply brought to the front.  What I’ve found is that LaunchDeskAccessory
  963. * doesn’t do anything but return opWrErr.  If the file that the desk accessory
  964. * is in is locked, then a new copy of the desk accessory is opened.  I didn’t
  965. * quite like either of those actions, so I explicitly check to see whether the
  966. * desk accessory that I’m about to launch is already open.  If it is, then I
  967. * call SetFrontProcess myself to bring it to the front.
  968. *
  969. * To check for this case, I first make sure that I have a name for the desk
  970. * accessory.  LaunchDeskAccessory accepts NIL as a desk accessory name if the
  971. * caller wants to launch the first desk accessory that the Process Manager finds
  972. * in the specified file.  My routine, LaunchProcess, also allows this.  But I
  973. * don’t know what the Process Manager thinks is the first desk accessory in the
  974. * file.  Since I’m comparing the characteristics of the desk accessory I’m about
  975. * to open against the list of open processes, and since I don’t know which desk
  976. * accessory is about to be launched if I leave the choice up to the Process
  977. * Manager, I just go and get the first desk accessory in the specified file
  978. * myself by calling my routine, GetFirstDAName.  Now that I’m guaranteed to have
  979. * a name for the desired desk accessory, I don’t have to second-guess the
  980. * Process Manager’s choice for the “first” desk accessory in the specified file.
  981. *
  982. * You can’t tell LaunchDeskAccessory that you want to terminate after the desk
  983. * accessory is launched, so I terminate myself by calling ExitToShell if the
  984. * terminate parameter was set to TRUE.  You also can’t tell LaunchDeskAccessory
  985. * that you want the desk accessory launched into the background.  I still
  986. * haven’t thought of a clean way to do this, so for now, I don’t offer that as
  987. * an option.  I can think of a few dirty ways.
  988. *******************************************************************************)
  989.  
  990.     FUNCTION LaunchProcDA (processFile:     FSSpec;
  991.                            daName:          StringPtr;
  992.                            options:         LaunchFlags;
  993.                            VAR processNum:  ProcessSerialNumber;
  994.                            VAR launchError: OSErr): OSErr;
  995.  
  996.         VAR
  997.             procInfo:    ProcessInfoRec;      {Used to search open processes}
  998.             firstDAName: Str255;              {File’s 1st DA name if none spec’d}
  999.             daOpen:      Boolean;             {TRUE if spec’d DA is already open}
  1000.             error:       OSErr;
  1001.  
  1002.     BEGIN
  1003.         error := noErr;
  1004.         launchError := noErr;
  1005.  
  1006.         (* If no DA name specified, get name of first DA in processFile *)
  1007.         IF daName = NIL THEN
  1008.             IF GetFirstDAName (processFile, (*<*)firstDAName) THEN
  1009.                 IF firstDAName [0] > CHR (0) THEN
  1010.                     daName := @firstDAName
  1011.                 ELSE
  1012.                     daName := NIL;
  1013.  
  1014.         (* If got name for new DA, see if DA process open w/ same name and file *)
  1015.         IF daName <> NIL THEN
  1016.             daOpen := FindProcess (processFile, daName, (*<*)procInfo)
  1017.         ELSE
  1018.             daOpen := FALSE;
  1019.  
  1020.         (* Launch the DA to the front, or set it to front if already launched *)
  1021.         IF daOpen THEN
  1022.             error := SetFrontProcess (procInfo.processNumber)
  1023.         ELSE
  1024.             launchError := LaunchDeskAccessory (processFile, daName^);
  1025.  
  1026.         (* If the terminate flag is set, glad to oblige *)
  1027.         IF BAND (options, launchContinue) = 0 THEN
  1028.             ExitToShell;
  1029.  
  1030.         (* Return the process serial number of the DA *)
  1031.         IF (error = noErr) AND (launchError = noErr) THEN
  1032.             BEGIN
  1033.                 daOpen := FindProcess (processFile, daName, (*<*)procInfo);
  1034.                 IF daOpen THEN
  1035.                     processNum := procInfo.processNumber
  1036.                 ELSE
  1037.                     BEGIN
  1038.                         procInfo.processNumber.highLongOfPSN := 0;
  1039.                         procInfo.processNumber.lowLongOfPSN := kNoProcess;
  1040.                         processNum := procInfo.processNumber
  1041.                     END
  1042.             END;
  1043.  
  1044.         LaunchProcDA := error
  1045.     END;
  1046.  
  1047.  
  1048. {$S ProcessUtils}
  1049. (*******************************************************************************
  1050. * Public: LaunchProcess
  1051. *
  1052. * The terminate and foreground parmeters are used to set up the flags for
  1053. * launching in the launchOptions variable.  The launch parameter block is set up
  1054. * with the specified file and launch flags.
  1055. *
  1056. * If the launchContinue flags is clear, then the LaunchApplication function
  1057. * doesn’t return even if it fails.
  1058. *******************************************************************************)
  1059.  
  1060.     FUNCTION LaunchProcess (processFile:     FSSpec;
  1061.                             daName:          StringPtr;
  1062.                             docList:         DocListHnd;
  1063.                                     options:         LaunchFlags;
  1064.                             VAR returnPSN:   ProcessSerialNumber;
  1065.                                     VAR launchError: OSErr): OSErr;
  1066.  
  1067.         VAR
  1068.             fileInfo: FInfo;               {File information for file to launch}
  1069.             newPSN:   ProcessSerialNumber; {PSN of launched application}
  1070.             error:    OSErr;
  1071.  
  1072.         PROCEDURE RecoverError (errorCode: OSErr);
  1073.  
  1074.         BEGIN
  1075.             LaunchProcess := errorCode;
  1076.             EXIT (LaunchProcess)
  1077.         END;
  1078.  
  1079.     BEGIN
  1080.         launchError := noErr;
  1081.  
  1082.         (* Find out whether file to launch is APPL or something else *)
  1083.         error := FSpGetFInfo (processFile, (*<*)fileInfo);
  1084.         IF error <> noErr THEN
  1085.             RecoverError (error);
  1086.  
  1087.         (* Launch the process *)
  1088.         IF fileInfo.fdType = 'APPL' THEN
  1089.             BEGIN
  1090.                 (* File to be launched is an application; launch it *)
  1091.                 error := LaunchProcApp (processFile, docList, options, (*<*)newPSN,
  1092.                         (*<*)launchError);
  1093.                 IF error <> noErr THEN
  1094.                     RecoverError (error);
  1095.             END
  1096.         ELSE
  1097.             BEGIN
  1098.                 (* File to be launch is a desk accessory; launch it *)
  1099.                 error := LaunchProcDA (processFile, daName, options, (*<*)newPSN,
  1100.                         (*<*)launchError);
  1101.                 IF error <> noErr THEN
  1102.                     RecoverError (error)
  1103.             END;
  1104.  
  1105.         (* Return the PSN of the new process *)
  1106.         IF launchError = noErr THEN
  1107.             returnPSN := newPSN;
  1108.  
  1109.         LaunchProcess := noErr
  1110.     END;
  1111.  
  1112.  
  1113. {$S ProcessUtils}
  1114. (*******************************************************************************
  1115. * Public: CountProcesses
  1116. *
  1117. * It’s a pretty simple and straightforward algorithm.
  1118. *******************************************************************************)
  1119.  
  1120.     FUNCTION CountProcesses: Integer;
  1121.  
  1122.         VAR
  1123.             procNum:   ProcessSerialNumber; {Serial num of process being checked}
  1124.             procCount: Integer;             {Number of processes found}
  1125.  
  1126.     BEGIN
  1127.         (* Start checking from first process in Process Manager’s list *)
  1128.         procNum.highLongOfPSN := 0;
  1129.         procNum.lowLongOfPSN := kNoProcess;
  1130.  
  1131.         (* Loop through entire list of open processes *)
  1132.         procCount := 0;
  1133.         WHILE GetNextProcess ((*◊*)procNum) = noErr DO
  1134.             procCount := SUCC (procCount);
  1135.  
  1136.         (* Return the number of processes found *)
  1137.         CountProcesses := procCount
  1138.     END;
  1139.  
  1140.  
  1141. {$S ProcessUtils}
  1142. (*******************************************************************************
  1143. * Public: TerminateProcess
  1144. *
  1145. * Terminating a process is done by sending a 'quit' AppleEvent to the process
  1146. * whose process serial number is given by "theProcessNum".
  1147. *******************************************************************************)
  1148.  
  1149.     FUNCTION TerminateProcess (theProcessNum: ProcessSerialNumber): OSErr;
  1150.  
  1151.         VAR
  1152.             theDoomed: AEAddressDesc; {PSN descriptor of process to be terminated}
  1153.             quitEvent: AppleEvent;    {'quit' AppleEvent}
  1154.             reply:     AppleEvent;    {Reply from receiving application; ignored}
  1155.             error:     OSErr;
  1156.  
  1157.         PROCEDURE RecoverError (error: Integer);
  1158.  
  1159.             VAR
  1160.                 result: OSErr;
  1161.  
  1162.         BEGIN
  1163.             IF theDoomed.dataHandle <> NIL THEN
  1164.                 result := AEDisposeDesc ((*◊*)theDoomed);
  1165.             IF quitEvent.dataHandle <> NIL THEN
  1166.                 result := AEDisposeDesc ((*◊*)quitEvent);
  1167.             TerminateProcess := error;
  1168.             EXIT (TerminateProcess)
  1169.         END;
  1170.  
  1171.     BEGIN
  1172.         theDoomed.dataHandle := NIL;
  1173.         quitEvent.dataHandle := NIL;
  1174.         reply.dataHandle := NIL;
  1175.  
  1176.         (* Create the Process Serial Number event descriptor *)
  1177.         error := AECreateDesc (typeProcessSerialNumber, Ptr(@theProcessNum),
  1178.                 SIZEOF (theProcessNum), (*<*)theDoomed);
  1179.         IF error <> noErr THEN
  1180.             RecoverError (error);
  1181.  
  1182.         (* Create 'quit' event with the specified process serial number *)
  1183.         error := AECreateAppleEvent (kCoreEventClass, kAEQuitApplication,
  1184.                 theDoomed, kAutoGenerateReturnID, kAnyTransactionID, (*<*)quitEvent);
  1185.         IF error <> noErr THEN
  1186.             RecoverError (error);
  1187.  
  1188.         (* Send the 'quit' event *)
  1189.         error := AESend (quitEvent, (*<*)reply, kAENoReply,
  1190.                 kAENormalPriority, kNoTimeOut, NIL, NIL);
  1191.         IF error <> noErr THEN
  1192.             RecoverError (error);
  1193.  
  1194.         (* PSN in the AppleEvent, so can dispose of PSN descriptor *)
  1195.         error := AEDisposeDesc ((*◊*)theDoomed);
  1196.  
  1197.         (* Dispose of the 'quit' AppleEvent *)
  1198.         error := AEDisposeDesc ((*◊*)quitEvent);
  1199.         
  1200.         TerminateProcess := noErr;
  1201.     END;
  1202.  
  1203. END.
  1204.